package net

import (
	"fmt"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"go.uber.org/zap"
	"sync/atomic"
	"time"
)

var (
	pongWait              = 10 * time.Second //pong 用于读
	writeWait             = 10 * time.Second //ping 用于写
	pingInerval           = 8 * time.Second  //ping的频率
	cidBase        uint64 = 10000
	maxMessageSize int64  = 1024
)

/*
*
这里边处理websocket的读写消息操作，心跳操作
*/
type WsConnection struct {
	Cid       string
	Conn      *websocket.Conn
	Manager   *Manager
	ReadChan  chan *MsgPack //读消息的通道
	WriteChan chan []byte   //写消息的通道
	Session   *Session
}

/*
*
获取session
*/
func (wc *WsConnection) GetSession() *Session {
	return wc.Session
}

/*
*
初始化WsConnection
*/
func NewWsConnection(conn *websocket.Conn, manager *Manager) *WsConnection {
	cid := fmt.Sprintf("%s-%s-%d", uuid.New().String(), manager.ServerId, atomic.AddUint64(&cidBase, 1))
	return &WsConnection{
		Cid:       cid,
		Conn:      conn,
		Manager:   manager,
		WriteChan: make(chan []byte, 1024), //设置有缓冲的数组,提高效率这个参数可以根据具体情况配置
		ReadChan:  manager.ClientReadChan,
		Session:   NewSession(cid),
	}
}

/*
*
读写协程
*/
func (wc *WsConnection) Run() {
	go wc.readMessage()
	go wc.writeMessage()
	//做一些心跳检测 类似于websocket ping pong机制
	wc.Conn.SetPongHandler(wc.PongHandler)
}

/*
*
读消息
*/
func (wc *WsConnection) readMessage() {
	//这一块代码为什么这样????????
	defer func() {
		wc.Manager.removeClient(wc)
	}()
	//设置pong延长时间
	wc.Conn.SetReadLimit(maxMessageSize) //设置最大读的字节数
	if err := wc.Conn.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
		zap.S().Errorf("readMessage SetReadDeadline err:%v", err)
		return //这里边一定要返回
	}
	for {
		messageType, msg, err := wc.Conn.ReadMessage()
		//获取的是二进制的消息
		if err != nil { //获取不到消息则终止
			break
		}
		if messageType == websocket.BinaryMessage {
			if wc.ReadChan != nil {
				zap.S().Infof("read读取到的消息是:%v", string(msg))
				wc.ReadChan <- &MsgPack{
					Cid:  wc.Cid,
					Body: msg,
				}
			}
		} else if messageType == websocket.TextMessage {
			zap.S().Infof("get text info:%s", string(msg))
		} else if messageType == -1 {
			zap.S().Infof("get unknown info:%s", string(msg))
		} else {
			zap.S().Errorf("unsuported message type:%d", messageType)
		}
	}
}

/*
*
服务端写消息
*/
func (wc *WsConnection) writeMessage() {
	ticker := time.NewTicker(pingInerval)
	for {
		select {
		case message, ok := <-wc.WriteChan:
			if !ok {
				//没有要写的消息，则关闭通道
				err := wc.Conn.WriteMessage(websocket.CloseMessage, nil)
				if err != nil {
					zap.S().Errorf("connectioned closed err:%v", wc.Cid, err)
				}
				return
			}
			//开始发送二进制的消息
			if err := wc.Conn.WriteMessage(websocket.BinaryMessage, message); err != nil {
				zap.S().Errorf("client[%s] write Message err:%v", wc.Cid, err)
			}
		case <-ticker.C:
			if err := wc.Conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
				zap.S().Errorf("client[%s] ping setWriteDeadline err:%v", wc.Cid, err)
				return //这里边一定要返回
			}
			if err := wc.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				zap.S().Errorf("client[%s] ping  err:%v", wc.Cid, err)
				return
			}
			zap.S().Infof("client[%s] ping  success", wc.Cid)
		}
	}
}

/*
*
关闭 实现接口的方法
*/
func (wc *WsConnection) Close() {
	if wc.Conn != nil {
		wc.Conn.Close()
	}
}

/*
*
server端发送消息
manager调用这个方法 发送消息 实现接口的方法
*/
func (wc *WsConnection) SendMessage(buf []byte) error {
	wc.WriteChan <- buf
	return nil
}

/*
*
pong方法
*/
func (wc *WsConnection) PongHandler(data string) error {
	err := wc.Conn.SetReadDeadline(time.Now().Add(pongWait))
	if err != nil {
		return err
	}
	zap.S().Info("pong...")
	return nil
}
